package com.minimalist.basic.config.aspect;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.minimalist.basic.service.OperatorLogService;
import com.minimalist.common.entity.basic.enums.StatusEnum;
import com.minimalist.common.entity.basic.vo.operatorlog.OperatorLogVO;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;

/**
 * @author 11987
 */
@Aspect
@Component
public class LoggingAspect{
    private static final Logger log = LoggerFactory.getLogger(LoggingAspect.class);

    private final OperatorLogService operatorLogService;

    public LoggingAspect(OperatorLogService operatorLogService){
        this.operatorLogService = operatorLogService;
    }

    @Pointcut("execution(* com.minimalist.*.controller..*(..)) && !execution(* com.minimalist.*.controller.api..*(..))")
    public void controllerMethods(){
    }

    @Around("controllerMethods()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable{
        HttpServletRequest request = ((ServletRequestAttributes) Objects
                .requireNonNull(RequestContextHolder.getRequestAttributes()))
                .getRequest();
        OperatorLogVO operatorLogVO = new OperatorLogVO();
        operatorLogVO.setRequestTime(LocalDateTime.now());
        String url = request.getRequestURI();
        String method = request.getMethod();
        // 排除GET请求和API路径的请求
        if ("GET".equals(method) || url.contains("/api/")){
            return joinPoint.proceed();
        }
        String remoteAddr = request.getRemoteAddr();
        String headers = request.getHeader("User-Agent");
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method proceedMethod = methodSignature.getMethod();
        Operation operation = proceedMethod.getAnnotation(Operation.class);
        Object[] args = joinPoint.getArgs();

        // 检查是否为退出登录操作
        boolean isLogout = url.contains("/logout") ||
                (operation != null && operation.summary() != null &&
                        operation.summary().contains("退出登录"));

        // 如果不是退出登录，按正常流程执行
        if (!isLogout){
            long startTime = System.currentTimeMillis();
            Object proceed = joinPoint.proceed();
            long endTime = System.currentTimeMillis();

            // 记录日志
            recordOperatorLog(operatorLogVO, remoteAddr, headers, operation, method, url,
                    args, proceed, startTime, endTime, request);

            return proceed;
        } else{
            // 退出登录操作，先记录日志再执行方法
            log.info("检测到退出登录操作，先记录日志再执行退出");

            long startTime = System.currentTimeMillis();

            // 先填充日志信息
            operatorLogVO.setIp(remoteAddr);
            operatorLogVO.setUserAgent(headers);
            if (operation != null){
                operatorLogVO.setName(getOperatorLogName(operation.summary()));
            } else{
                operatorLogVO.setName("退出登录");
            }
            operatorLogVO.setUri(url);
            operatorLogVO.setMethod(method);

            // 从方法参数中提取createId、updateId和tenantId
            extractIdsFromArgs(args, operatorLogVO);

            // 检查是否为文件上传请求
            String contentType = request.getContentType();
            if (contentType == null || !contentType.contains("multipart/form-data")){
                operatorLogVO.setParams(JSON.toJSONString(args));
            }

            // 设置状态为成功
            operatorLogVO.setStatus(StatusEnum.STATUS_1.getCode());
            operatorLogVO.setResult("{\"message\":\"退出登录成功\",\"statusCodeValue\":\"200\"}");
            // 先设置为0，后面更新
            operatorLogVO.setExceedTime("0");

            // 保存日志
            operatorLogService.addOperatorLog(operatorLogVO);

            // 执行退出登录操作
            Object proceed = joinPoint.proceed();

            // 更新耗时
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;

            // 由于日志已经保存，这里不再更新日志
            log.info("退出登录操作完成，耗时: {}ms", duration);

            return proceed;
        }
    }

    /**
     * 记录操作日志
     */
    private void recordOperatorLog(OperatorLogVO operatorLogVO, String remoteAddr
            , String headers, Operation operation, String method, String url
            , Object[] args, Object proceed, long startTime, long endTime
            , HttpServletRequest request){
        operatorLogVO.setIp(remoteAddr);
        operatorLogVO.setUserAgent(headers);
        operatorLogVO.setExceedTime(String.valueOf(endTime - startTime));
        if (operation != null){
            operatorLogVO.setName(getOperatorLogName(operation.summary()));
        } else{
            operatorLogVO.setName(getOperatorLogName(method));
        }
        operatorLogVO.setUri(url);
        operatorLogVO.setMethod(method);
        // 检查是否为文件上传请求
        String contentType = request.getContentType();
        if (contentType != null && contentType.contains("multipart/form-data")){
            return;
        }

        // 从方法参数中提取createId、updateId和tenantId
        extractIdsFromArgs(args, operatorLogVO);

        operatorLogVO.setParams(JSON.toJSONString(args));
        String result = JSON.toJSONString(proceed);
        Map<String, String> map = JSON.parseObject(result, new TypeReference<>(){
        });
        if ("200".equals(map.get("statusCodeValue"))){
            operatorLogVO.setStatus(StatusEnum.STATUS_1.getCode());
        } else{
            operatorLogVO.setStatus(StatusEnum.STATUS_0.getCode());
        }
        operatorLogVO.setResult(result);
        operatorLogService.addOperatorLog(operatorLogVO);
    }

    private String getOperatorLogName(String summary){
        if (summary == null || StrUtil.isBlank(summary)){
            return "";
        }
        if (summary.contains("：")){
            return summary.split("：")[0];
        } else{
            return summary.split("\\s+")[0];
        }
    }

    /**
     * 从方法参数中提取createId、updateId和tenantId
     *
     * @param args          方法参数
     * @param operatorLogVO 操作日志VO
     */
    private void extractIdsFromArgs(Object[] args, OperatorLogVO operatorLogVO){
        if (args == null){
            return;
        }

        for (Object arg : args){
            if (arg == null){
                continue;
            }

            // 将参数转为JSON字符串，然后解析为Map
            String json = JSON.toJSONString(arg);
            try{
                Map<String, Object> map = JSON.parseObject(json, new TypeReference<Map<String, Object>>(){
                });

                // 检查并提取createId
                if (map.containsKey("createId") && map.get("createId") != null){
                    Object createId = map.get("createId");
                    if (createId instanceof Number){
                        operatorLogVO.setCreateId(((Number) createId).longValue());
                    }
                }

                // 检查并提取updateId
                if (map.containsKey("updateId") && map.get("updateId") != null){
                    Object updateId = map.get("updateId");
                    if (updateId instanceof Number){
                        operatorLogVO.setUpdateId(((Number) updateId).longValue());
                    }
                }

                // 检查并提取tenantId (注意：OperatorLogVO中的字段名是tanantId，可能是拼写错误)
                if (map.containsKey("tenantId") && map.get("tenantId") != null){
                    Object tenantId = map.get("tenantId");
                    if (tenantId instanceof Number){
                        operatorLogVO.setTenantId(((Number) tenantId).longValue());
                    }
                } else if (map.containsKey("tanantId") && map.get("tanantId") != null){
                    Object tanantId = map.get("tanantId");
                    if (tanantId instanceof Number){
                        operatorLogVO.setTenantId(((Number) tanantId).longValue());
                    }
                }
            } catch (Exception e){
                log.debug("从参数中提取ID信息失败: {}", e.getMessage());
            }
        }
    }
}

